﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using System.Data;
using System.Diagnostics;
using Lizk.Utilities.Net;

namespace Lizk.Media
{
    public class VolumeScanner
    {
        public int MaxDepth { get; set; }
        public List<TypeScanner> Scanners { get; private set; }
        private TestData set;
        private Dictionary<string, Importer> importers = new Dictionary<string, Importer>();
        public object CompletionLock = new object();
        public bool Complete { get; set; }

        //public ImageCache ImageCache { get; set; }

        private FileCache[] caches;

        public event EventHandler ScanBegun;
        public event EventHandler DatabaseUpdateBegun;
        public event EventHandler ScanComplete;

        public VolumeScanner(TestData set, params FileCache[] caches)
        {
            Scanners = new List<TypeScanner>();
            this.set = set;
            Complete = true;
            this.caches = caches;
        }

        public void Scan(params string[] roots)
        {
            if (roots.Length == 0)
                roots = FindAllMediaRoots(DriveType.Fixed, DriveType.Network);


            lock (CompletionLock)
            {
                var doneImporters = (from a in importers
                                    where a.Value.Complete
                                    select a.Key).ToArray();
                foreach (var i in doneImporters)
                {
                    importers.Remove(i);
                }

                for (int i = 0; i < roots.Length; i++)
                {
                    DriveInfo di = new DriveInfo(roots[i]);
                    if (!importers.ContainsKey(di.VolumeLabel))
                    {
                        
                        if (importers.Count == 0)
                        {
                            Complete = false;
                            if (ScanBegun != null)
                                ScanBegun(this, EventArgs.Empty);
                        }



                        importers.Add(di.VolumeLabel, new Importer() { Path = roots[i] });
                        
                        ThreadPool.QueueUserWorkItem(new WaitCallback(BeginScan), importers[di.VolumeLabel]);
                    }
                }
            }
        }

        private void BeginScan(object obj)
        {
            Importer importer = (Importer)obj;
            for (int i = 0; i < Scanners.Count; i++)
            {
                if (Directory.Exists(Path.Combine(importer.Path, Scanners[i].Type)))
                {
                    Scanners[i].Scan(importer.Path, Exists, Added);

                }

            }


            lock (set.LockObject)
            {
                DeleteMissingMedia(importer.Path);
            }


            lock (CompletionLock)
            {
                importer.Complete = true;
                importer.Message = "Import Complete";
                if (importer.DeleteCount > 0 || importer.AddCount > 0)
                {
                    string tmp = "\n";
                    if (importer.AddCount > 0)
                        tmp += "New: " + importer.AddCount + " ";
                    if (importer.DeleteCount > 0)
                        tmp += "Removed: " + importer.DeleteCount;
                    importer.Message += tmp;
                }
                
                foreach (Importer i in importers.Values)
                {
                    if (i.Complete == false)
                        return;
                }

                // All importers have finished at this point!!!!!!

                if (DatabaseUpdateBegun != null)
                    DatabaseUpdateBegun(this, null);

                lock (set.LockObject)
                {
                    foreach (Importer i in importers.Values)
                    {
                        AddQueue(i.AdditionQueue);
                        while (i.DeletionQueue.Count > 0)
                        {
                            i.DeletionQueue.Dequeue().Delete();
                        }
                    }
                }

                lock (set.LockObject)
                {
                    RemoveUnusedTitles(set);
                }
                Complete = true;
            }
            if (ScanComplete != null)
                ScanComplete(this, EventArgs.Empty);


        }

        private void Added(VideoData v)
        {
            if (importers.ContainsKey(v.volume))
            {
                Importer i = importers[v.volume];
                i.AdditionQueue.Enqueue(v);
                i.Message = "Added: " + Path.GetFileName(v.path);
                i.AddCount++;

/*                if (Monitor.TryEnter(set.LockObject))
                {
                    AddQueue(i.AdditionQueue);
                    Monitor.Exit(set.LockObject);
                }*/
            }
            
        }

        private void AddQueue(Queue<VideoData> videoData)
        {
            while (videoData.Count > 0)
            {
                VideoData v = videoData.Dequeue();

                TestData.TitlesRow title = (from t in set.Titles
                                            where t.Name.Equals(v.show, StringComparison.CurrentCultureIgnoreCase)
                                            select t).FirstOrDefault();


                if (title == null)
                {
                    
                    
                    title = set.Titles.AddTitlesRow(v.show, v.type, "");

                    foreach (var cache in caches)
                    {
                        if (cache.Contains(title))
                        {
                            string existing = cache.FindExisting(GetShowPath(title.Name, Path.Combine(GetRoot(v.volume), v.path)));
                            if (existing != null)
                            {
                                cache.Save(title, existing);
                            }
                        }
                    }
                    

                }

                long time = v.lastmodified > DateTime.Now.ToFileTime() ? DateTime.Now.ToFileTime() : v.lastmodified;
                if (v.lastmodified > DateTime.Now.ToFileTime())
                    File.SetLastWriteTime(Path.Combine(GetRoot(v.volume), v.path), DateTime.Now);
                
                TestData.FilesRow file = set.Files.AddFilesRow(v.volume, v.path, title, DateTime.Now.Date.ToFileTime(), time);
                

                if (v.lastepisode <= 0)
                    v.lastepisode = v.episode;

                if(v.episode != 0)
                    for (int i = v.episode; i <= v.lastepisode; i++)
                    {
                        TestData.EpisodesRow episode = (from e in title.GetEpisodesRows()
                                                        where e.Episode == i && e.Season == v.season
                                                        select e).FirstOrDefault();

                        if (episode == null)
                        {
                            episode = set.Episodes.AddEpisodesRow(title, "", v.season, i, v.description, 0);
                        }

                        set.EpisodeFiles.AddEpisodeFilesRow(episode, file.Volume, file.Path);
                    }


            }
        }

        private bool Exists(VideoData v)
        {
            return set.Files.Rows.Contains(new object[] { v.volume, v.path });
        }

        public static void RemoveUnusedTitles(TestData set)
        {
            foreach (TestData.TitlesRow title in set.Titles)
            {
                if (title.GetFilesRows().Length == 0)
                {
                    title.Delete();
                }
            }
        }

        private void DeleteMissingMedia(string rootPath)
        {
            string volume = (new DriveInfo(rootPath)).VolumeLabel;

            if(!importers.ContainsKey(volume))
                return;

            foreach (TestData.FilesRow row in (from file in set.Files where file.Volume == volume select file))
            {
                if (!File.Exists(Path.Combine(rootPath, row.Path)))
                {
                    importers[volume].DeletionQueue.Enqueue(row);
                    importers[volume].DeleteCount++;
                }
            }
        }

        public string GetMessage(string volume)
        {
            if (importers.ContainsKey(volume))
                return importers[volume].Message;

            return "";
        }

        public bool IsImporting(string volume)
        {
            if (importers.ContainsKey(volume))
                return !importers[volume].Complete;

            return false;
        }

        private class Importer
        {
            public bool Complete { get; set; }
            public string Message { get; set; }
            public Queue<VideoData> AdditionQueue { get; set; }
            public Queue<TestData.FilesRow> DeletionQueue { get; set; }
            public int AddCount { get; set; }
            public int DeleteCount { get; set; }
            public string Path { get; set; }
            public Importer()
            {
                AddCount = 0;
                DeleteCount = 0;
                AdditionQueue = new Queue<VideoData>();
                DeletionQueue = new Queue<TestData.FilesRow>();
                Message = "Importing...";
            }
        }

        private object lockObject = new object();
        private List<Root> onlineDrives = null;
        private DriveType[] acceptedTypes = new DriveType[] { DriveType.Network, DriveType.Fixed };

	    public DriveType[] AcceptedTypes
	    {
		    get { return acceptedTypes;}
		    set { acceptedTypes = value;}
	    }

        public List<Root> OnlineVolumes
        {
            get {
                return onlineDrives;
            }
        }

        public string GetVolume(string path)
        {
            lock (lockObject)
            {
                DriveInfo di = new DriveInfo(path);
                foreach (var label in onlineDrives)
                {
                    if (label.Volume == di.VolumeLabel)
                        return label.Volume;
                }
                return null;
            }
        }

        public void RefreshVolumes()
        {
            lock (lockObject)
            {

                onlineDrives = new List<Root>();
                foreach (DriveInfo di in DriveInfo.GetDrives())
                    if (Array.IndexOf(acceptedTypes, di.DriveType) > -1)
                    {
                        if (di.DriveType == DriveType.Network && !Lizk.Utilities.Net.NetworkDrive.IsNetworkDriveOnline(di.RootDirectory.FullName, 1000))
                            continue;



                        if (di.IsReady)
                        {
                            string path = FindSingleMediaRoot(di.VolumeLabel, acceptedTypes);
                            if (path == null)
                                continue;

                            onlineDrives.Add(new Root() { Volume = di.VolumeLabel, Path = path });
                        }
                    }

                onlineDrives.Sort();
            }
        }

        public bool IsOnline(string volume)
        {
            lock (lockObject)
            {

                foreach (var root in onlineDrives)
                {
                    if (root.Volume == volume)
                        return true;
                }
                return false;
            }
        }

        public string GetRoot(string volume)
        {
            lock (lockObject)
            {
                foreach (var root in onlineDrives)
                {
                    if (root.Volume == volume)
                        return root.Path;
                }
                return null;
            }
        }

        public class Root : IComparable
        {
            public string Volume { get; set; }
            public string Path { get; set; }


            #region IComparable Members

            public int CompareTo(object obj)
            {
                Root other = obj as Root;
                if (other == null)
                    throw new ArgumentException("Object was not a Root");

                return Volume.CompareTo(other.Volume);
            }

            #endregion
        }


        public void Distibute(TestData.TitlesRow title, FileCache cache, bool overwrite)
        {
            List<string> volumes = new List<string>(OnlineVolumes.Count);

            foreach (TestData.FilesRow file in title.GetFilesRows())
            {
                if (volumes.Contains(file.Volume) || !IsOnline(file.Volume))
                    continue;

                volumes.Add(file.Volume);

                string titlePath = GetShowPath(title.Name, Path.Combine(GetRoot(file.Volume), file.Path));

                if (!Directory.Exists(titlePath))
                    continue;

                string existing = cache.FindExisting(titlePath);
                

                // If volume has an existing file
                if (existing != null)
                {
                    // and the cache does not
                    if (!cache.Contains(title))
                        // save it
                        cache.Save(title, existing);
                    else
                    {
                        cache.Distribute(title, titlePath);
                    }

                }
                else
                {
                    // cache contains the title but it is not on the volume
                    if (cache.Contains(title))
                    {
                        cache.Distribute(title, titlePath);
                    }
                }




            }

        }

        
        public void Distribute()
        {
            lock (CompletionLock)
            {
                foreach (TestData.TitlesRow title in set.Titles)
                {
                    List<string> volumes = new List<string>(OnlineVolumes.Count);

                    foreach (TestData.FilesRow file in title.GetFilesRows())
                    {
                        if (volumes.Contains(file.Volume) || !IsOnline(file.Volume))
                            continue;

                        volumes.Add(file.Volume);

                        string titlePath = GetShowPath(title.Name, Path.Combine(GetRoot(file.Volume), file.Path));

                        if (!Directory.Exists(titlePath))
                            continue;

                        foreach (FileCache c in caches)
                        {
                            string existing = c.FindExisting(titlePath);

                            // If volume has an existing file
                            if (existing != null)
                            {
                                // and the cache does not
                                if (!c.Contains(title))
                                    // save it
                                    c.Save(title, existing);

                            }
                            else
                            {
                                // cache contains the title but it is not on the volume
                                if (c.Contains(title))
                                {
                                    c.Distribute(title, titlePath);
                                }
                            }

                        }


                    }

                }
            }

        }
        public string GetShowPath(string show, string artworkPath)
        {
            string test = artworkPath.ToLower();
            while (Path.GetFileName(test) != show.ToLower() && test != null)
            {
                test = Path.GetDirectoryName(test);
            }
            return test;
        }

        public string FindMediaRoot(string path, int depth)
        {
            try
            {
                if (File.Exists(Path.Combine(path, ".mediadir")))
                    return path;

                if (path.ToLower().Contains("felizk"))
                {
                }

                foreach (var d in Directory.GetDirectories(path))
                {
                    string directory = Path.GetFileName(d);
                    
                    foreach (var scanner in Scanners)
                    {
                        if (directory.ToLower() == scanner.Type.ToLower())
                            return path;
                    }
                }


                if (depth > 0)
                    foreach (string d in Directory.GetDirectories(path))
                    {

                        string g = FindMediaRoot(d, depth - 1);
                        if (g != null)
                            return g;
                    }
            }
            catch
            {
                return null;
            }
            return null;
        }

        public string FindSingleMediaRoot(string volumeLabel, DriveType[] acceptedTypes)
        {
            string s = null;


            foreach (DriveInfo di in DriveInfo.GetDrives())
            {

                if (Array.IndexOf<DriveType>(acceptedTypes, di.DriveType) == -1)
                    continue;

                if (di.DriveType == DriveType.Network)
                {
                    if (!NetworkDrive.IsNetworkDriveOnline(di.RootDirectory.FullName, 1000))
                        continue;

                    if (!di.IsReady)
                    {
                        try
                        {
                            NetworkDrive.RestoreConnection(di.Name);
                        }
                        catch
                        {
                            continue;
                        }
                    }
                }
                else if (!di.IsReady)
                    continue;

                if (di.VolumeLabel != volumeLabel)
                    continue;



                s = FindMediaRoot(di.RootDirectory.FullName, MaxDepth);
                break;
            }
            return s;
        }
        public string[] FindAllMediaRoots(params DriveType[] acceptedTypes)
        {
            List<string> mediaroots = new List<string>();

            foreach (DriveInfo di in DriveInfo.GetDrives())
            {
                if (Array.IndexOf<DriveType>(acceptedTypes, di.DriveType) == -1)
                    continue;

                if (di.DriveType == DriveType.Network)
                {
                    if (!NetworkDrive.IsNetworkDriveOnline(di.RootDirectory.FullName, 1000))
                        continue;

                    if (!di.IsReady)
                    {
                        try
                        {
                            NetworkDrive.RestoreConnection(di.Name);
                        }
                        catch
                        {
                            continue;
                        }
                    }
                }
                else if (!di.IsReady)
                    continue;

                string s = FindMediaRoot(di.RootDirectory.FullName, MaxDepth);
                if (s != null)
                    mediaroots.Add(s);
            }
            return mediaroots.ToArray();
        }
    }
}
